package javapop.framework.parser.expr;

import java.util.ArrayList;
import java.util.List;

import javapop.framework.DefaultParseResult;
import javapop.framework.ParseContext;
import javapop.framework.ParseError;
import javapop.framework.ParseInput;
import javapop.framework.ParseResult;
import javapop.framework.Parser;
import javapop.utils.Pair;
import javapop.utils.Triple;

public class Expression<T extends ExprNode> extends Parser<T> {
	private String name;
	private String description;
	private List<Parser<?>> skipParsers;
	private List<Parser<? extends OperandNode>> operandParsers;
	private List<Pair<Parser<? extends PostfixNode>,Integer>> postfixParsers;
	private List<Pair<Parser<? extends PrefixNode>,Integer>> prefixParsers;
	private List<Triple<Parser<? extends InfixNode>,Integer,ExprAssoc>> infixParsers;
	private List<Parser<?>> openParsers;
	private List<Parser<?>> closeParsers;

	public Expression(String name) {
		this.name = "<"+name+">";
		description = null;
		skipParsers = new ArrayList<Parser<?>>();
		operandParsers = new ArrayList<Parser<? extends OperandNode>>();
		postfixParsers = new ArrayList<Pair<Parser<? extends PostfixNode>,Integer>>();
		prefixParsers = new ArrayList<Pair<Parser<? extends PrefixNode>,Integer>>();
		infixParsers = new ArrayList<Triple<Parser<? extends InfixNode>,Integer,ExprAssoc>>();
		openParsers = new ArrayList<Parser<?>>();
		closeParsers = new ArrayList<Parser<?>>();
	}

	public Expression<T> skip(Parser<?> skipParser) {
		skipParsers.add(skipParser);
		return this;
	}

	public Expression<T> operand(Parser<? extends OperandNode> operandParser) {
		operandParsers.add(operandParser);
		return this;
	}

	public Expression<T> postfix(int prio, Parser<? extends PostfixNode> postfixParser) {
		postfixParsers.add(new Pair<Parser<? extends PostfixNode>, Integer>(postfixParser,prio));
		return this;
	}

	public Expression<T> prefix(int prio, Parser<? extends PrefixNode> prefixParser) {
		prefixParsers.add(new Pair<Parser<? extends PrefixNode>, Integer>(prefixParser,prio));
		return this;
	}

	public Expression<T> infix(int prio, ExprAssoc assoc, Parser<? extends InfixNode> infixParser) {
		infixParsers.add(new Triple<Parser<? extends InfixNode>, Integer,ExprAssoc>(infixParser,prio,assoc));
		return this;
	}

	public Expression<T> bracket(Parser<?> openParser, Parser<?> closeParser) {
		openParsers.add(openParser);
		closeParsers.add(closeParser);
		return this;
	}

	public String getName() {
		return name;
	}
	
	@Override
	public String getDescription() {
		if(description==null) {
			StringBuilder buf = new StringBuilder();
			buf.append(name); buf.append(" ::=\n");
			for(Triple<Parser<? extends InfixNode>,Integer,ExprAssoc> infixParser : infixParsers) {
				buf.append("   | ");
				buf.append(name);
				buf.append(" ");
				buf.append(infixParser.getFirst());
				buf.append(" ");
				buf.append(name);
				buf.append("\n");
			}
			for(Pair<Parser<? extends PrefixNode>,Integer> prefixParser : prefixParsers) {
				buf.append("   | ");
				buf.append(prefixParser.getFirst());
				buf.append(" ");
				buf.append(name);
				buf.append("\n");
			}
			for(Pair<Parser<? extends PostfixNode>,Integer> postfixParser : postfixParsers) {
				buf.append("   | ");
				buf.append(name);
				buf.append(" ");
				buf.append(postfixParser.getFirst());
				buf.append("\n");
			}
			for(int i=0;i<openParsers.size();i++) {
				buf.append("   | ");
				buf.append(openParsers.get(i));
				buf.append(" ");
				buf.append(name);
				buf.append(" ");
				buf.append(closeParsers.get(i));
				buf.append("\n");
			}
			for(Parser<? extends OperandNode> operandParser : operandParsers) {
				buf.append("   | ");
				buf.append(operandParser);
				buf.append("\n");
			}
				
			description = buf.toString();
		}
		return description;
	}
	
	@Override
	public boolean isMemoized() {
		return false;
	}

	@SuppressWarnings("unchecked")
	private ParseResult<? extends ExprNode> parseOne(ParseContext<?> ctx, ParseInput input, ExprContext ectx) {
		// apply skip parsers first
		boolean skipMore = false;
		do {
			skipMore = false;
			for(Parser<?> skipParser : skipParsers) {
				int savePos = input.getPos();
				ParseResult<?> result = skipParser.parse(ctx,input);
				if(!result.isError() && input.getPos()>savePos) {
					skipMore = true;
				}
			}
		} while(skipMore);

		// try to parse an operand
		if(ectx.expectExpr()) {
			for(Parser<? extends OperandNode> operandParser : operandParsers) {
				ParseResult<? extends OperandNode> result = operandParser.parse(ctx,input);
				if(!result.isError()) {
					ectx.append(result.getResult());
					ectx.expectExpr(false);
					return result;
				}
			}
		}

		// try to parse a unary postfix operator
		if(!ectx.expectExpr()) {
			for(Pair<Parser<? extends PostfixNode>,Integer> postfixParser : postfixParsers) {
				ParseResult<? extends PostfixNode> result = postfixParser.getFirst().parse(ctx,input);
				if(!result.isError()) {
					result.getResult().setPrio(postfixParser.getSecond());
					ectx.append(result.getResult());
					return result;
				}
			}
		}

		// try to parse a unary prefix operator
		if(ectx.expectExpr()) {
			for(Pair<Parser<? extends PrefixNode>,Integer> prefixParser : prefixParsers) {
				ParseResult<? extends PrefixNode> result = prefixParser.getFirst().parse(ctx,input);
				if(!result.isError()) {
					result.getResult().setPrio(prefixParser.getSecond());
					ectx.push(result.getResult());
					return result;
				}
			}
		}

		// try to parse an infix operator
		if(!ectx.expectExpr()) {
			for(Triple<Parser<? extends InfixNode>,Integer,ExprAssoc> infixParser : infixParsers) {
				ParseResult<? extends InfixNode> result = infixParser.getFirst().parse(ctx, input);
				if(!result.isError()) {
					result.getResult().setPrio(infixParser.getSecond());
					result.getResult().setAssoc(infixParser.getThird());

					switch(infixParser.getThird()) {
					case LEFT: {
						boolean finish = false;
						while(!finish) {
							if(!ectx.hasTop()) { // stack is empty
								finish = true;
								continue;
							} else { // there is a top element on the stack
								ExprNode top = ectx.pop();
								int prioOfTop = -1;
								if(top.isInfix()) {
									prioOfTop = top.asInfix().getPrio();
								} else if(top.isPrefix()) {
									prioOfTop = top.asPrefix().getPrio();
								} else {
									ectx.push(top);
									finish = true;
									continue;
								}
								// compare the priority
								if(prioOfTop>=infixParser.getSecond()) {
									ectx.append(top); // append the top
								} else {
									ectx.push(top); // push back the top
									finish = true;
									continue;
								}							
							}
						} // while
					}
					break;
					case RIGHT: {
						boolean finish = false;
						while(!finish) {
							if(!ectx.hasTop()) { // stack is empty
								finish = true;
								continue;
							} else { // there is a top element on the stack
								ExprNode top = ectx.pop();
								int prioOfTop = -1;
								if(top.isInfix()) {
									prioOfTop = top.asInfix().getPrio();
								} else if(top.isPrefix()) {
									prioOfTop = top.asPrefix().getPrio();
								} else {
									ectx.push(top);
									finish = true;
									continue;
								}
								// compare the priority
								if(prioOfTop>infixParser.getSecond()) {
									ectx.append(top); // append the top
								} else {
									ectx.push(top); // push back the top
									finish = true;
									continue;
								}							
							}
						} // while
					}
					break;
					case NO: // Do nothing is not associative
					} // switch

					// common case : put the result on stack
					ectx.push(result.getResult());
					ectx.expectExpr(true);
					return result;
				}
			} // for
		}

		// try to parse brackets
		for(int i=0;i<openParsers.size();i++) {
			if(ectx.expectExpr()) {
				Parser<?> openParser = openParsers.get(i);
				ParseResult<?> result = openParser.parse(ctx, input);
				if(!result.isError()) {
					OpenNode openNode = new OpenNode(openParser,closeParsers.get(i));
					ectx.push(openNode);
					ectx.open(); // open the bracket
					ParseResult<? extends ExprNode> oresult = new DefaultParseResult<OpenNode>(OpenNode.class,openNode);
					oresult.setStart(result.getStart());
					oresult.setEnd(result.getEnd());
					return oresult;
				}
			} else if(ectx.getNestLevel()>0) { // only for nest level > 0
				// does not expect an expression, so may expect a close bracket
				Parser<?> closeParser = closeParsers.get(i);
				ParseResult<?> result = closeParser.parse(ctx, input);
				if(!result.isError()) {
					ectx.close();  // close the bracket
					boolean finish = false;
					while(!finish) {
						if(!ectx.hasTop()) {
							return (ParseResult) new ParseError("Expecting an opening bracket for: "+closeParser.getDescription());
						}
						// stack is not empty
						ExprNode top = ectx.pop();
						if((top instanceof OpenNode) && ((OpenNode) top).getCloseParser()==closeParser) {
							finish = true;
							continue;
						}
						// append top to output
						ectx.append(top);
					}
					ParseResult<? extends ExprNode> cresult = new DefaultParseResult<CloseNode>(CloseNode.class,new CloseNode(openParsers.get(i),closeParser));
					cresult.setStart(result.getStart());
					cresult.setEnd(result.getEnd());
					return cresult;
				}
			}
		} // for

		if(ectx.expectExpr()) {
			return (ParseResult) new ParseError("Expecting an operand, a prefix operator or an open bracket");
		} else {
			return (ParseResult) new ParseError("Expecting a postfix operator, an infix operator or a close bracket");
		}

	}

	@SuppressWarnings("unchecked")
	@Override
	public ParseResult<T> eval(ParseContext<?> ctx, ParseInput input) {
		ExprContext ectx = new ExprContext();
		boolean parsed = false;

		while(true) {
			ParseResult<? extends ExprNode> result = null;
			if(!input.isEOF()) {
				result = parseOne(ctx, input, ectx);
				if(!result.isError()) {
					// ok the expression continues...
					parsed = true;
					continue;
				}
			}

			// if input is EOF or is the result is a parse error
			if(!parsed) {
				if(result!=null && result.isError()) {
					return (ParseResult) result;
				} else {
					return (ParseResult) new ParseError("Expecting an expression");
				}
			}

			// something has been parsed
			if(ectx.expectExpr()) {
				// cannot stop in the middle of the expression
				return (ParseResult) new ParseError("Expecting more operands in expression");
			}

			// ok now we empty the stack
			while(ectx.hasTop()) {
				ExprNode top = ectx.pop();
				if(top instanceof OpenNode) {
					return (ParseResult) new ParseError("Expecting a close bracket in expression");
				}
				ectx.append(top);
			}

			ParseResult<T> ast = (ParseResult<T>) ectx.generateAST();
			if(ast.isError()) {
				return (ParseResult) ast;
			}
			return buildResult(ast.getResult());				
		}

	} // while
}
	
